package com.abso.mp3tunes.locker.ui;

import java.io.IOException;
import java.net.URL;

import javazoom.jl.decoder.Bitstream;
import javazoom.jl.decoder.BitstreamException;
import javazoom.jl.decoder.Decoder;
import javazoom.jl.decoder.Header;
import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.decoder.SampleBuffer;
import javazoom.jl.player.AudioDevice;
import javazoom.jl.player.FactoryRegistry;

import org.eclipse.core.runtime.ListenerList;

import com.abso.mp3tunes.locker.core.data.Track;

/**
 * The MP3Player is a thread based on the JLayer player, which is able to play a
 * specific track.
 */
public class MP3Player extends Thread {

	/** The list of listeners. */
	private ListenerList listeners = new ListenerList();

	/** The played track. */
	private Track track;

	/** Indicates whether the current track is paused. */
	private boolean paused = false;

	/**
	 * Indicates whether the player is active (playing or paused), or is
	 * stopped.
	 */
	private boolean playing = true;

	/** The current bit stream. */
	private Bitstream stream;

	/** The current decoder. */
	private Decoder decoder;

	/** The current audio device. */
	private AudioDevice audio;

	/** Lock object used to synchronize the pause action. */
	private Object pauseLock = new Object();

	/**
	 * Constructs a new MP3 player.
	 * 
	 * @param track
	 *            the track to play.
	 */
	public MP3Player(Track track) {
		this.track = track;
	}

	/**
	 * Adds a player listener.
	 * 
	 * @param listener
	 *            the listener being added.
	 */
	public void addMP3PlayerListener(IMP3PlayerListener listener) {
		listeners.add(listener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Thread#run()
	 */
	public void run() {
		play();
	}

	/** Plays the current track. */
	private void play() {
		try {
			stream = new Bitstream(new URL(track.getPlayURL()).openStream());
			decoder = new Decoder();
			FactoryRegistry r = FactoryRegistry.systemRegistry();
			audio = r.createAudioDevice();
			audio.open(decoder);
			int frames = Integer.MAX_VALUE;
			boolean ret = true;
			while ((frames-- > 0) && ret && playing) {
				synchronized (pauseLock) {
					if (paused) {
						try {
							pauseLock.wait();
						} catch (InterruptedException ex) {
							return;
						}
					}
				}
				ret = decodeFrame();
			}
			if (!ret && playing) {
				Object[] playerListeners = listeners.getListeners();
				for (int i = 0; i < playerListeners.length; ++i) {
					((IMP3PlayerListener) playerListeners[i]).playerComplete();
				}
			}
		} catch (IOException e) {
		} catch (JavaLayerException e) {
		} finally {
			stopPlayer();
		}
	}

	/**
	 * Decodes a frame an plays it.
	 * 
	 * @return <code>true</code> if the decoding is correctly performed.
	 * @throws JavaLayerException
	 */
	protected boolean decodeFrame() throws JavaLayerException {
		try {
			Header h = stream.readFrame();
			if (h == null) {
				return false;
			}
			SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, stream);
			synchronized (this) {
				audio.write(output.getBuffer(), 0, output.getBufferLength());
			}
			stream.closeFrame();
		} catch (Throwable e) {
			throw new JavaLayerException("Exception decoding audio frame", e);
		}
		return true;
	}

	/** Stops the player. */
	public void stopPlayer() {
		playing = false;
		paused = false;
		if (audio != null) {
			audio.close();
		}
		if (stream != null) {
			try {
				stream.close();
			} catch (BitstreamException e) {
			}
		}
		listeners.clear();
	}

	/** Pauses the player. */
	public void pausePlayer() {
		setPaused(true);
	}

	/**
	 * Sets the pause status of the player.
	 * 
	 * @param paused
	 *            the new pause status.
	 */
	public void setPaused(boolean paused) {
		if (this.paused != paused) {
			synchronized (pauseLock) {
				this.paused = paused;
				if (!paused) {
					pauseLock.notifyAll();
				}
			}
		}
	}

	/**
	 * Indicates whether the player is paused.
	 * 
	 * @return <code>true</code> if the player is paused.
	 */
	public boolean isPaused() {
		return paused;
	}

}
